package top.itbook;

import top.itbook.pojo.User;
import top.itbook.shop.Armor;
import top.itbook.shop.Arms;
import top.itbook.shop.ShopArmor;
import top.itbook.shop.ShopArms;
import top.itbook.txt.ClassTestVersion;
import top.itbook.util.*;
import top.itbook.util.scanner.InputInt;

import java.io.*;
import java.lang.ref.WeakReference;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Scanner;
import java.util.concurrent.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 自创练手修仙小游戏
 * 作者：小王
 * 联系方式：
 *  QQ：3272737852
 *  Wechat：w3272737852
 * 项目版本：V0.9
 */
public class Main {
    // 集合
    final public static ArrayList<User> users = new ArrayList<>(); // 用户集集合
    final public static ArrayList<Arms> armsShop = new ArrayList<>(); // 系统武器商店
    final public static ArrayList<Armor> armorShop = new ArrayList<>(); // 系统防具商店

    // 软件架构依赖变量
    final public static MD2 md2 = (MD2) EncryptionAlgorithms.createEncryptionAlgorithms("MD2");
    final public static MD5 md5 = (MD5) EncryptionAlgorithms.createEncryptionAlgorithms("MD5");
    final public static SHA1 sha1 = (SHA1) EncryptionAlgorithms.createEncryptionAlgorithms("SHA-1");
    final public static SHA224 sha224 = (SHA224) EncryptionAlgorithms.createEncryptionAlgorithms("SHA-224");
    final public static String USER_FILE = "C:\\GamesDemo\\User.txt"; // 用户本地缓存文件地址
    public static ObjectInputStream inputStream = null; // 用于读取本地用户缓存文件的对象管道

    // 线程池
    /**
     * 核心线程：3
     * 临时线程：7
     * 临时线程最大存活时间：600秒
     * 线程队列最大任务数：5
     */
    public static ExecutorService service = new ThreadPoolExecutor(3,10,
            600, TimeUnit.SECONDS,
            new ArrayBlockingQueue<>(5), Executors.defaultThreadFactory(),
            new ThreadPoolExecutor.AbortPolicy());

    // 使用静态代码块以准备程序所需资源
    static{
        // 任意Arms或Armor类的对象商品都可以调用addCommodity方法将其添加到对应的商店中
        // 武器添加示例 - 在本系统中任何类中都可添加如下方法以添加武器
        Arms swords = new ShopArms("木剑",5,10,"木质的剑身，新手村必备！",true);
        Arms machete = new ShopArms("砍刀",10,30,"前期保命良好选择，经济实惠！",true);
        swords.addCommodity(); // 此方法是添加进武器商品库的API 内部已封装好，无需将武器集合导入到类或传递到方法中。且任何地方都可使用，操作者无需知道武器商店集合也可进行添加操作
        machete.addCommodity();

        // 防具添加示例 - 在本系统中任何类中都可使用如下方式为系统商城添加武器
        Armor woodenShield = new ShopArmor("木盾",5,10,"新手保命最佳选择，您的不二之选！",true);
        Armor pavise = new ShopArmor("大盾",15,15,"让新手前期不再那么脆弱，就是好像有点不划算~",true);
        woodenShield.addCommodity(); // 此方法是添加进防具商品库的API 内部已封装好，无需将防具集合导入到类或传递到方法中。且任何地方都可使用，操作者无需知道防具商店集合也可进行添加操作
        pavise.addCommodity();
    }

    public static void main(String[] args) {
        // 提交任务给线程池进行处理
        Runnable runnable = new Gc();
        service.execute(runnable);

        // 本地缓存文件内信息
        User cache = null; // 本地缓存

        // 拦截并捕获异常
        try {
            File file = new File(USER_FILE);
            // 判断是否存在本地缓存文件，如果有则加载本地缓存以实现联网登录速度优化顺带减轻数据库压力
            if (file.exists()){
                // 存在本地用户缓存，进行加载
                // 创建对象字节输入流管道包装低级字节输入流管道
                inputStream = new ObjectInputStream(new FileInputStream(USER_FILE));
                // 调用对象反序列化方法
                cache = (User) inputStream.readObject();
            }
            // 判断是否已经加载本地缓存，如果有，顺带加进集合中，防止再注册时出现用户账号冲突（也就是防止出现有2个账户的账号都是1）情况
            if (cache != null){
                users.add(cache);
            }

            // 加密对象
            // SHA-256加密类实例化对象
            final SHA256 SHA256 = (SHA256) EncryptionAlgorithms.createEncryptionAlgorithms("SHA-256");


            // 对象 - 之小王有对象
            final Scanner scanner = new Scanner(System.in); // 键盘扫描器

            // 系统主界面
            userMenu(scanner,SHA256,Main.users,Main.armsShop,cache);
        } catch (Exception e) {
            // 打印异常栈信息
            e.printStackTrace();
        }finally{
            service.shutdownNow(); // 因为有线程使用死循环，就不等任务全都执行完再关闭了
            // 关闭对象字节输入流管道
            try {
                inputStream.close();
            } catch (IOException e) {}
        }
    }

    /**
     * 主选单
     */
    public static void userMenu(Scanner scanner,SHA256 sha256,ArrayList<User> users,ArrayList<Arms> armsShop,User cache){
        while (true) {
            System.out.println("温馨提示：如上一次是按正常逻辑退出的则使用账号1进行快速登录还可以读取上一次游戏记录。如遇密码正确但是却登录失败的情况请手动进入C盘下把Games缓存文件夹删除即可");
            System.out.println("******************************");
            System.out.println("1.登录");
            System.out.println("2.注册");
            System.out.println("3.使用协议");
            System.out.println("4.软件更新详情");
            System.out.println("5.软件介绍");
            System.out.println("6.删除本地缓存");
            System.out.println("7.退出软件");
            System.out.println("******************************");
            System.out.print("请输入对应选项的序号：");
            int index = InputInt.obtainInput();
            switch(index)
            {
                case 1: // 登录
                    login(scanner,users,sha256,armsShop,cache);
                    break;
                case 2: // 注册
                    registerUser(scanner,sha256,users);
                    break;
                case 3: // 使用协议
                    userProtocol();
                    break;
                case 4: // 软件更新详情
                    versionIntroduction();
                    break;
                case 5: // 软件介绍
                    introduce();
                    break;
                case 6: // 删除本地缓存
                    try {
                        delCache();
                    } catch (IOException e) {
                        System.err.println("本地缓存文件删除失败，请前往C盘目录下的GamesDemo文件夹内手动删除User.txt文件");
                        e.printStackTrace();
                    }
                    break;
                case 7: // 退出软件
                    System.out.println("欢迎下次继续使用这个从来不修BUG的软件");
                    Runtime.getRuntime().exit(0); // 终结JVM
                    break;
                default:
                    System.out.println("对不起，未能找到您输入的选项所对应的功能");
            }
        }
    }

    /**
     * 删除本地用户缓存文件
     * @throws IOException 如果管道关闭失败则抛出IO异常
     */
    public static void delCache() throws IOException {
        System.out.print("请问您是否删除本地缓存(Y\\N)：");
        Scanner scanner = new Scanner(System.in);
        String str = scanner.next();
        if (str.equals("Y") || str.equals("y")){
            // 执行删除逻辑
            File file = new File(Main.USER_FILE);
            if (file.exists()){
                // 存在本地缓存，进行删除
                // 删除前先将文件管道关闭
                inputStream.close();
                boolean delete = file.delete(); // 返回文件是否成功删除
                if (delete){
                    System.out.println("本地缓存删除成功！请重新打开此软件！");
                    // 让程序休眠5秒
                    try {
                        TimeUnit.SECONDS.sleep(5);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.exit(0); // 关闭虚拟机以强制退出
                }else{
                    System.out.println("本地文件删除失败，请手动前往C盘的GamesDemo文件夹下删除User.txt文件");
                }
            }else{
                // 根本不存在本地缓存文件，这里直接提示成功清除
                System.out.println("成功清除本地缓存文件。");
            }
        }
    }

    /**
     * 注册新用户
     */
    public static void registerUser(Scanner scanner,SHA256 sha256,ArrayList<User> users) {
        // 定义正则表达式 - 检验密码安全度
//        String s = "^[\\w]{6,}[\\D]+$"; // 密码长度最少6位且要包含最少一位字母 若符合要求则返回true
        String s = "^(?=.*[a-z])(?=.*[A-Z])[a-zA-Z0-9~!@&%#_]{8,16}$"; // 密码长度最少6位且要包含最少一位字母 若符合要求则返回true
        // 编译正则表达式
        Pattern pattern = Pattern.compile(s);
        System.out.print("请设置您的用户名：");
        String name = scanner.next();
        scanner = new Scanner(System.in); // 重新生成以防发生缓冲区未空问题
        System.out.print("请设置您的密码：");
        String password = scanner.next();
        Matcher matcher = pattern.matcher(password);
        // 循环判断密码是否符合正则表达式的要求 - 返回ture则满足要求
        while (true) {
            if (matcher.matches() == false){
                System.out.println("您的密码有问题，不可以使用过于简单的密码或低于6位数的密码且最少包含一个大写字母和小写字母！");
                System.out.print("请重新输入您的密码：");
                scanner = new Scanner(System.in);
                password = scanner.next();
                matcher = pattern.matcher(password);
            }
            if (matcher.matches() == true){
                // 退出死循环
                break;
            }
        }

        // 进行SHA-256哈希加密
        // 将字符串换成SHA-256散列值
        String hash = sha256.SHA256encryption(password);
        // 将内存中的password内存区清空 顺带请求一次垃圾清理
        // 创建弱引用，并让此引用到密码的字符串
        var stringPassword = new WeakReference(password);
        // 切断password和字符串之间的引用
        password = null;
        System.gc();
        System.runFinalization();

        int account = users.size() + 1;
        User user = new User(users.size() + 1, Integer.toString(account),hash,name,"普通用户");
        // 获取本地电脑日期时间对象
        LocalDateTime localDateTime = LocalDateTime.now();
        // 格式化日期格式
        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        user.setRegistrationDate(dateTimeFormatter.format(localDateTime)); // 按格式化日期解析时间并保存进用户变量中
        InetAddress net = null;
        try {
            net = InetAddress.getLocalHost();
        } catch (UnknownHostException e) {
            e.printStackTrace();
        }
        // 修改用户的注册IP
        if (net == null){
            System.err.println("对不起，程序执行出错，请联系开发者进行反馈！并建议您重新尝试注册 ~");
            return; // 终结方法
        }
        user.setRegIP(net.getHostAddress());
        user.setEncryption("SHA-256"); // 将用户所用的加密算法保存进用户对象
        users.add(user); // 将用户对象添加进集合中
        System.out.println("当前时间：" + dateTimeFormatter.format(localDateTime) + " 注册成功，欢迎新用户：" + user.getUsername() + " \n您被分配到的账号是： " + user.getAccount() + " 请妥善保管！");
    }

    /**
     * 用户登录
     */
    public static void login(Scanner scanner,ArrayList<User> users,SHA256 sha256,ArrayList<Arms> armsShop,User cache){
        // 判断当前系统是否已有用户
        if(users.size() == 0){
            System.out.println("对不起，系统中不存在任何账户，请去系统中进行注册后再来登录哦~");
            return; // 退出登录方法
        }
        else {
            // 执行登录逻辑
            System.out.print("请您输入账号：");
            String account = scanner.next();
            // 根据用户输入的账号进行查找
            // 先查找是否存在本地缓存
            if (cache != null){
                // 存在本地缓存，先与本地缓存进行对比
                if (account.equals(cache.getAccount())){
                    scanner = new Scanner(System.in);
                    // 账号与本地缓存匹配，进行匹配密码
                    System.out.print("请输入密码：");
                    String password = scanner.next();
                    String hash = ""; // 获取用户输入的密码的哈希值
                    hash = sha256.SHA256encryption(password);
                    if (hash.equals(cache.getPassword())){
                        System.out.println("快速登录成功！");
                        // 密码正确进入用户菜单界面
                        // 使用弱引用，让此引用引用到密码字符串
                        var string = new WeakReference(password);
                        // 切断password与密码字符串的引用
                        password = null;
                        // 通知大妈尽快收垃圾
                        System.gc();
                        System.runFinalization();
                        UserMenu.menu(cache,sha256,armsShop,armorShop);
                        return;
                    }
                }
            }
            // 不存在本地缓存，走正常的遍历集合方式进行查询，此方法稍慢一些，而且集合过大可能造成电脑遍历时间久的情况。
            System.out.println("请稍后~ 系统正在努力查找中");
            User user = getAccountByCardId(users, account);
            if(user == null){
                System.out.println("对不起，系统中查无此账户，请检查输入是否有误！");
            }else{
                // 查找到对应用户
                System.out.print("请输入密码：");
                scanner = new Scanner(System.in);
                String password = scanner.next();
                String hash = ""; // 保存用户输入的密码的哈希值
                // 将用户输入的明文密码转换成散列值与用户集合进行对比
                String encryption = user.getEncryption(); // 获取用户当前使用的加密算法
                // 根据获取到的正在登录的用户所使用的加密算法获取哈希
                if (encryption.equals("MD2")){
                    // 获取实例
                    MD2 md2 = Main.md2;
                    // 将用户输入的明文密码转换成十六进制哈希值
                    // 这里按道理一般情况下不会出现调用实例方法报null错误，但是IDEA还是警告我了，加一个也无所谓
                    if (md2 == null){
                        System.err.println("软件出错！请联系开发者！");
                        return;
                    }
                    hash = md2.md2encryption(password);
                }
                if (encryption.equals("MD5")){
                    // 获取实例
                    MD5 md5 = Main.md5;
                    if (md5 == null){
                        System.err.println("软件出错！请联系开发者！");
                        return;
                    }
                    // 获取哈希
                    hash = md5.md5encryption(password);
                }
                if (encryption.equals("SHA-1")){
                    SHA1 sha1 = Main.sha1;
                    if (sha1 == null){
                        System.err.println("软件出错！请联系开发者！");
                        return;
                    }
                    hash = sha1.SHA1encryption(password);
                }
                if (encryption.equals("SHA-224")){
                    SHA224 sha224 = Main.sha224;
                    if (sha224 == null){
                        System.err.println("软件出错！请联系开发者！");
                        return;
                    }
                    hash = sha224.SHA224encryption(password);
                }
                if (encryption.equals("SHA-256")){
                    if (sha256 == null){
                        System.err.println("软件出错！请联系开发者！");
                        return;
                    }
                    hash = sha256.SHA256encryption(password);
                }
                if (hash.equals(user.getPassword())){
                    // 密码正确进入用户菜单界面
                    // 使用弱引用，让此引用引用到密码字符串
                    var string = new WeakReference(password);
                    // 切断password与密码字符串的引用
                    password = null;
                    // 通知大妈尽快收垃圾
                    System.gc();
                    System.runFinalization();
                    UserMenu.menu(user,sha256,armsShop,armorShop);
                }else{
                    System.out.println("对不起，您输入的密码不正确，请您重新输入！");
                }
            }
        }
    }

    /**
     * 历史版本更新记录
     */
    private static void versionIntroduction(){
        InputStream inputStream = null;
        BufferedInputStream buffer = null;
        try{
            // 创建一个文件字节输入流管道与源文件连通
            // 使用类加载器获取文件运行时路径
            inputStream = ClassTestVersion.get(); // 获取输入流
            // 字节缓冲输入流
            buffer = new BufferedInputStream(inputStream);
            byte[] bytes = new byte[1024];
            // 使用循环每次读取一个字节数组
            int count; // 记录每一次读取的字节数
            while((count = buffer.read(bytes)) != -1){
                // 将数组内的所有数据一次性倒出 - 文件编码为适应控制台而采用GBK
                System.out.print(new String(bytes,0,count,"GBK"));
            }
            System.out.println();
        }catch(Exception exception){
            // 打印错误栈信息
            exception.printStackTrace();
        }finally{
            try {
                if (inputStream != null) {
                    // 关闭管道要先关闭后创建的
                    buffer.close();
                    inputStream.close(); // 关闭管道
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 使用协议
     */
    private static void userProtocol() {
        System.out.println("欢迎使用本游戏软件，本游戏软件为作者进行JavaSE练手所写的一款小游戏软件");
        System.out.println("别看这个软件功能很少，但却是一款容纳了很多JavaSE技术的一款软件，当然，作者写做时是在疫情期间，刚开始写不到一个礼拜就被告知不久后解封就要上班了233");
        System.out.println("但是我不想这个项目就这么烂尾下去，所以决定在极短时间内写出来，大不鸟以后继续更下去就是了，于是本来想直接发布的1.0正式版变成了0.1开发中版-.-");
        System.out.println("当然，本软件会尽量秉承着开源的方式去进行发布，关于为什么没有GUI，第一点是我不会作图，第二点是，创建各个GUI界面无疑会增加不少代码量-.-当然，以后有条件了肯定开发一个可以让大家满意的Java项目");
        System.out.println("以后本项目中会留有许多作者本人不进行开发的可扩展性内容，只提供对应方法，学者可根据自身想法自行填充");
        System.out.println("项目允许使用者进行修改，其使用的开源协议为Apache2.0开源协议");
        System.out.println("任何人可进行二次开发或学习使用，需保留原作者著作信息！");
        System.out.println("当你注册本软件后即默认同意此协议！");
        System.out.println("祝您使用愉快~");
        try {
            Thread.sleep(1000*5); // 睡眠5秒以供阅读
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    /**
     * 此软件介绍
     */
    private static void introduce(){
        System.out.println("修仙小游戏软件介绍");
        System.out.println("\t此软件为小王在利用闲余时间为了复习JavaSE而开始管理的项目，同时也是为了可以让大家可以在此项目基础上进行修改的写法。");
        System.out.println("\n\t此项目因为其所含硬实力很低，毕竟我也说了是一个练手的项目，所以这个项目建议仅对Java或编程有一定爱好而非专业人士详细了解并学习~");
        System.out.println("\t关于这个项目很重要的一点：这个项目中我知道有很多代码逻辑有很大问题的地方，大多数是故意的，因为我想要在每个版本留下一些比较明显的问题让你们可以找到并修复的地方，以锻炼新手爱好者的编程能力~");
        System.out.println("\t若你是第一次解除此软件想了解原理的话请尽可能从V0.1版本开始看起，因为V0.1中我故意留下了很多的代码问题，而这些问题我会保留一段时间后在公开会存在什么问题。");
        System.out.println("当然，还是那句话，如果是专业人士请勿在此浪费时间，此代码中所有故意留下的错误仅仅是给小白练习用的！主要是给仅仅是有爱好的人或打算学计算机编程准备入门的人的小练手。");
        System.out.println("当然，本作中除了有关我（也就是原作）的信息以外，你可以随意进行修改，但是禁止进行盈利行为！本软件禁止搬运！");
        System.out.println("第二版开始将会开放一些可以自定义的内容，敬请期待。");
        System.out.println("若你是一名对计算机有爱好，且有一定充足的时间的人士，若不介意可以加入开发小组，需要了解一定的JavaSE知识内容。当然，本人经济实力有限，没有办法给开发团队发放工资，所以是无偿的，若有兴趣有意愿的可以报名。");
        System.out.println("另外，小组开发需要会使用一些版本控制技术，比如git等！");
        System.out.println("感谢您对作者本人的支持！");
    }

    /**
     * 根据传入的账号查询符合的账号对象并返回
     * @param users 现有用户集合
     * @param account 要查找的用户对象账号
     * @return 单个用户对象 - 若没有找到则返回null
     */
    private static User getAccountByCardId(ArrayList<User> users,String account){
        for (User user : users) {
            if (account.equals(user.getAccount())){
                return user;
            }
        }
        return null; // 没有找到则返回空
    }
}